/**
 * 
 */
package org.stars.daostars.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.daostars.DaoBase;
import org.stars.daostars.conversion.TypeHandler;
import org.stars.util.StringUtility;
import org.stars.util.reflect.AccessorPair;
import org.stars.util.reflect.BeanUtil;
import org.stars.util.reflect.ReflectUtil;

/**
 * Rappresenta una mappa definita all'interno di un file dao.xml. <code><pre>
 *        &lt;map name=&quot;&quot;&gt;
 *        	&lt;attribute from=&quot;id&quot; to=&quot;id&quot; /&gt;
 *        	&lt;attribute from=&quot;azienda&quot; to=&quot;azienda&quot; /&gt;
 *        &lt;/map&gt;
 * </pre></code>
 * 
 * @author Francesco Benincasa (908099)
 * 
 */
public class DaoMap implements Serializable {

	/**
	 * serial id
	 */
	private static final long	serialVersionUID	= -2389337990104614491L;

	public static String		MAP_ALL_ATTRIBUTES	= "MAP_ALL_ATTRIBUTES";

	/**
	 * Get logger
	 * 
	 * @return logger
	 */
	public static Log getLog() {
		return LogFactory.getLog(DaoMap.class);
	}

	public DaoMap() {
		this("");
	}

	/**
	 * Effettua la copia degli attributi tra un oggetto ed un altro. I due
	 * oggetti possono essere di tipo diverso.
	 * <p>
	 * Da notare
	 * 
	 * @param beanSrc
	 * @param beanDest
	 * @return <code>true</code> se tutto è andato bene.
	 * @throws Exception
	 */
	public boolean execute(Object beanSrc, Object beanDest) throws Exception {
		if (propertyFromSet.size() > 0 && "*".equals(propertyFromSet.get(0))) {
			return allExecute(beanSrc, beanDest);
		}
		return normalExecute(beanSrc, beanDest);
	}

	/**
	 * Crea una lista di oggetti partendo da una lista di altri oggetti gia'
	 * definiti.
	 * <p>
	 * 
	 * @param listSrc
	 * @param beanClassSrc
	 * @param beanClassDest
	 * @param <S>
	 * @param <D>
	 * @return la nuova lista di oggetti
	 * @throws Exception
	 *             in caso di errore
	 * 
	 */
	public <S, D> List<D> execute(List<S> listSrc, Class<S> beanClassSrc, Class<D> beanClassDest) throws Exception {
		if (propertyFromSet.size() > 0 && "*".equals(propertyFromSet.get(0))) {
			return allExecute(listSrc, beanClassSrc, beanClassDest);
		}
		// TODO: da implementare la copia completa
		return allExecute(listSrc, beanClassSrc, beanClassDest);
	}

	/**
	 * Effettua una copia degli attributi da un oggetto ad un altro. La copia
	 * viene effettuata per referenza, quindi per gli attributi definiti come
	 * classe si crea un duplicato di referenza allo stessa istanza di classe.
	 * <p>
	 * Il match tra gli attributi viene effettuato mediante il nome. Si cerca di
	 * copiare tutti gli attributi dell'oggetto <code>beanSrc</code> che hanno
	 * in <code>beanDest</code> un attributo con nome analogo.
	 * 
	 * @param beanSrc
	 *            bean sorgente
	 * @param beanDest
	 *            bean destinazione
	 * @return <code>true</code> se e' andato tutto bene.
	 * @throws Exception
	 *             in caso di errore
	 */
	public static boolean copy(Object beanSrc, Object beanDest) throws Exception {
		return allExecute(beanSrc, beanDest);
	}

	/**
	 * Dati due entity bean, questo metodo effettua il passaggio tra un
	 * 
	 * @param beanSrc
	 *            EntityBean sorgente
	 * @param beanDest
	 *            EntityBean destinazione
	 * @return true se ok, false altrimenti
	 * @throws Exception
	 *             Eccezzione
	 */
	private static boolean allExecute(Object beanSrc, Object beanDest) throws Exception {
		Log log = getLog();
		long start;
		long end;

		start = System.currentTimeMillis();

		BeanUtil.copy(beanSrc, beanDest);		
		end = System.currentTimeMillis();
		if (DaoBase.isGlobalPrintMode()) {
			log.info("Time for execute map: " + (end - start) + " ms");
		}
		return true;
	}

	/**
	 * Data una lista di entity bean, ottiene
	 * 
	 * @param beanSrc
	 *            EntityBean sorgente
	 * @param beanDest
	 *            EntityBean destinazione
	 * @return true se ok, false altrimenti
	 * @throws Exception
	 *             Eccezzione
	 */
	private static <S, D> List<D> allExecute(List<S> listSrc, Class<S> beanClassSrc, Class<D> beanClassDest) throws Exception {
		Log log = getLog();
		long start;
		long end;

		start = System.currentTimeMillis();

		AccessorPair[] props = BeanUtil.join(beanClassSrc, beanClassDest);

		List<D> retList = new ArrayList<D>();
		// oggetto da inserire nella lista di ritorno
		D itemDest;
		Object temp;

		for (S itemSrc : listSrc) {
			itemDest = Helper.makeObject(beanClassDest);

			for (AccessorPair m : props)
			{
				temp=null;
				try {
					temp = m.getter.invoke(itemSrc);
					m.setter.invoke(itemDest, temp);
				} catch (Exception e) {
					log.warn("Map " + itemSrc.getClass() + " a " + itemDest.getClass() + ": attributo (" + m.getter.getPropertyName() + "->" + m.setter.getPropertyName()
							+ ") impossibile assegnare il valore " + temp + " da classe " + m.getter.getPropertyType() + " a classe " + m.setter.getPropertyType());
				}

			}
			retList.add(itemDest);
		}

		end = System.currentTimeMillis();
		if (DaoBase.isGlobalPrintMode()) {
			log.info("Time for execute map (" + beanClassSrc.getSimpleName() + " -> " + beanClassDest.getSimpleName() + "): " + (end - start) + " ms. Element(s) in list: "
					+ retList.size() + " (from a list of " + listSrc.size() + " elements).");
		}
		return retList;
	}

	/**
	 * Dati due entity bean, questo metodo effettua il passaggio tra un
	 * 
	 * @param beanSrc
	 *            EntityBean sorgente
	 * @param beanDest
	 *            EntityBean destinazione
	 * @return true se ok, false altrimenti
	 * @throws Exception
	 *             Eccezzione
	 */
	private boolean normalExecute(Object beanSrc, Object beanDest) throws Exception {
		
		long start;
		long end;

		start = System.currentTimeMillis();

		BeanUtil.copy(beanSrc, beanDest);

		end = System.currentTimeMillis();

		if (DaoBase.isGlobalPrintMode()) {
			Log log = getLog();
			log.info("Time for execute map: " + (end - start) + " ms");
		}

		return true;
	}

	/**
	 * @param propertyName
	 * @param value
	 * @return
	 * @throws Exception
	 */
	public Object convertJavaToDatabase(String propertyName, Object value) throws Exception {
		String property = propertyName.toUpperCase();

		if (propertyTypeHandler.containsKey(property)) {
			TypeHandler handler = propertyTypeHandler.get(property);

			return handler.javaToDatabase(value);

		} else {
			return value;
		}
	}

	/**
	 * @param propertyName
	 * @param value
	 * @return
	 * @throws Exception
	 */
	public Object convertDatabaseToJava(String propertyName, Object value) throws Exception {
		String property = propertyName.toUpperCase();

		if (propertyTypeHandler.containsKey(property)) {
			TypeHandler handler = propertyTypeHandler.get(property);

			return handler.databaseToJava(value);

		} else {
			return value;
		}
	}

	public DaoMap(String sName) {
		name = StringUtility.nvl(sName);
		propertyFromSet = new LinkedList<String>();
		propertyNameSet = new LinkedList<String>();
		propertyTypeHandler = new Hashtable<String, TypeHandler>();
	}

	private String							name;

	private LinkedList<String>				propertyFromSet;

	private LinkedList<String>				propertyNameSet;

	private Hashtable<String, TypeHandler>	propertyTypeHandler;

	@Override
	public String toString() {
		return ReflectUtil.inspectObject(this, new StringBuilder().append(this.getClass().getSimpleName()).append("{")).reverse().delete(0, 2).reverse().append("}").toString();
	}

	public TypeHandler getTypeHandler(String propertyName) {
		propertyName = StringUtility.nvl(propertyName).toUpperCase();

		if (!propertyTypeHandler.containsKey(propertyName))
			return null;

		TypeHandler handler = propertyTypeHandler.get(propertyName);

		return handler;
	}

	/**
	 * Getter dell'attributo attributeDestinationSet.
	 * 
	 * @return the attributeDestinationSet
	 */
	public LinkedList<String> getPropertyNameSet() {
		return propertyNameSet;
	}

	/**
	 * Setter dell'attributo attributeDestinationSet.
	 * 
	 * @param attributeDestinationSet
	 *            the attributeDestinationSet to set
	 */
	public void setPropertyNameSet(LinkedList<String> attributeDestinationSet) {
		this.propertyNameSet = attributeDestinationSet;
	}

	/**
	 * Getter dell'attributo attributeSourceSet.
	 * 
	 * @return the attributeSourceSet
	 */
	public LinkedList<String> getPropertyFromSet() {
		return propertyFromSet;
	}

	/**
	 * Setter dell'attributo attributeSourceSet.
	 * 
	 * @param attributeSourceSet
	 *            the attributeSourceSet to set
	 */
	public void setPropertyFromSet(LinkedList<String> attributeSourceSet) {
		this.propertyFromSet = attributeSourceSet;
	}

	/**
	 * Definisce una mappatura tra un attributo ed un altro.
	 * 
	 * @param sName
	 *            nome dell'attributo in cui copiare il valore
	 * @param sFrom
	 *            nome dell'attributo che contiene il valore da copiare
	 */
	public void addPropertyMapping(String sName, String sFrom) {
		propertyFromSet.add(sFrom);
		propertyNameSet.add(sName);
	}

	/**
	 * Definisce una mappatura tra un attributo ed un altro.
	 * 
	 * @param sName
	 *            nome dell'attributo in cui copiare il valore
	 * @param sFrom
	 *            nome dell'attributo che contiene il valore da copiare
	 */
	public void addPropertyMapping(String sName, String sFrom, TypeHandler typeHandler) {
		propertyFromSet.add(sFrom);
		propertyNameSet.add(sName);

		sName = sName.toUpperCase();
		propertyTypeHandler.put(sName, typeHandler);
	}

	/**
	 * Getter dell'attributo name.
	 * 
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Setter dell'attributo name.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

}
